/**
 * 
 */
package com.googlecode.cswish.model;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;

import com.googlecode.cswish.struts.ContextHolder;
import com.googlecode.cswish.struts.hibernate.ISite;
import com.googlecode.cswish.util.ClassUtil;


/**
 * It stores all the persistence properties and top interface properties
 * 
 * @author Jerry.Feng Date: 2008-4-10
 */
public class ModelInfo {

	private static final Logger logger = Logger.getLogger(ModelInfo.class);
	
	/**
	 * table name
	 */
	private String table;
	
	private String secondTable;
	
	/**
	 * class type
	 */
	private String type;
	
	private String genericType;
	
	private String simpleName;
	
	private boolean transientClass;

	/**
	 * it store all the model property
	 * property name , property info
	 */
	private Map<String, PropertyInfo> properties;
	
	private Map<String, List<String>> propertyReferences;
	
	private ModelInfo parent;

	public ModelInfo(ModelInfo parent) {
		this.parent = parent;
		properties = new HashMap<String, PropertyInfo>();
	}
	
	public String getTable() {
		return table;
	}
	
	public void setTable(String table) {
		this.table = table;
	}

	public String getSecondTable() {
		return secondTable;
	}

	public void setSecondTable(String secondTable) {
		this.secondTable = secondTable;
	}

	public String getSimpleName() {
		return simpleName;
	}
	
	public void setSimpleName(String simpleName) {
		this.simpleName = simpleName;
	}
	
	public PropertyInfo getProperty(String name) {
		PropertyInfo propertyInfo = properties.get(name);
		if (propertyInfo == null && parent != null) {
			propertyInfo = parent.getProperty(name);
		}
		return propertyInfo;
	}
	
	public PropertyInfo getDeclaredProperty(String name) {
		return properties.get(name);
	}
	
	public Collection<PropertyInfo> getDeclaredProperties() {
		return properties.values();
	}

	public PropertyInfo addProperty(PropertyInfo propertyInfo) {
		PropertyInfo old = properties.put(propertyInfo.getName(), propertyInfo);
		return old;
	}
	
	public void addRefProperty(String propertyName, String refBy) {
		if (propertyReferences == null) {
			propertyReferences = new HashMap<String, List<String>>();
		}
		
		List<String> reference = propertyReferences.get(propertyName);
		if (reference == null) {
			reference = new ArrayList<String>(1);
			propertyReferences.put(propertyName, reference);
		}
		
		reference.add(refBy);
	}
	
	// reference by
	public List<String> getRefByProperties(String propertyName) {
		if (propertyReferences == null) {
			return null;
		}
		
		return propertyReferences.get(propertyName);
	}
	
	public PropertyInfo removeProperty(Object propertyName) {
		PropertyInfo propertyInfo = properties.remove(propertyName);
		return propertyInfo;
	}
	
	public Collection<PropertyInfo> getAllProperties() {
		if (parent != null) {
			Collection<PropertyInfo> allParentProperties = parent.getAllProperties();
			List<PropertyInfo> allProperties = new ArrayList<PropertyInfo>(allParentProperties.size() + properties.size());
			allProperties.addAll(properties.values());
			for (PropertyInfo propertyInfo : allParentProperties) {
				// check and ignore the existed property
				if (!properties.containsKey(propertyInfo.getName())) {
					allProperties.add(propertyInfo);
				}
			}
			return allProperties;
		} else {
			return properties.values();
		}
	}
	
	public List<String> getAllPropertyNames(boolean includeSimpleProperty, boolean includeComplexProperty) {
		List<String> propertyNames = new ArrayList<String>();
		for (PropertyInfo propertyInfo : getAllProperties()) {
			if (includeSimpleProperty && includeComplexProperty
				|| includeSimpleProperty && propertyInfo.isSimpleObj()
				|| includeComplexProperty && !propertyInfo.isSimpleObj()) {
				propertyNames.add(propertyInfo.getName());
			}
		}
		return propertyNames;
	}

	/**
	 * the exact class type, List<String> --> List
	 * 
	 * @return
	 */
	public String getType() {
		return type;
	}
	
	public Class<?> getTypeClass() {
		return ClassUtil.getClass(type);
	}

	public void setType(String type) {
		this.type = type;
		
		// Default generic type is same with type
		if (this.genericType == null) {
			this.genericType = type;
		}
	}
	
	public String toString() {
		return (this.type != null ? this.type : "") + '<' 
		+ (this.genericType != null ? this.genericType : "") + '>';
	}
	
	public String getGenericType() {
		return genericType;
	}

	public void setGenericType(String genericType) {
		this.genericType = genericType;
	}
	
	public ModelInfo getParent() {
		return parent;
	}

	public void setParent(ModelInfo parent) {
		this.parent = parent;
	}

	public boolean isTransientClass(boolean checkTable) {
		if (checkTable) {
			return table == null;
		} else {
			return transientClass;
		}
	}

	public boolean isDynamicClass() {
		return isDynamicClass(type);
	}
	
	public static boolean isDynamicClass(String type) {
		return type != null && type.endsWith("_Dyna");
	}
	
	public static String getDynamicClassName(String className, int index) {
		String siteName = ContextHolder.get().getSiteName();
		if (siteName == null) {
			siteName = ISite.DEFAULT_NAME;
		}
		// TODO: ensure the index is less than 10?
		return className + '_' + siteName + index + "_Dyna";
	}
	
	public static String getStaticClassName(String className) {
		int index = className.lastIndexOf('_');
		if (index > 0) {
			// FIXME: correctly? twice?
			index = className.lastIndexOf('_', index - 1);
			if (index > 0) {
				return className.substring(0, index);
			}
		}
		return className;
	}
}